#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/of_irq.h>
#include <linux/clk.h>

#include "drv_cipher_ioctl.h"
#include "fh_error_mpi.h"
#include "hw_rpu.h"



CESA_RES_S g_cesa = {0};



static int cipher_open(struct inode *inode, struct file *filp)
{
	return FH_SUCCESS;
}
static int cipher_release(struct inode *inode, struct file *filp)
{
	return FH_SUCCESS;
}
static long cipher_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	char  sbuf[128];
    void  *parg = sbuf;
	CMD_CIPHER_CREATE_S *cmd_cipher_create = (CMD_CIPHER_CREATE_S *)sbuf;
	CMD_CIPHER_DESTROY_S *cmd_cipher_destroy = (CMD_CIPHER_DESTROY_S *)sbuf;
	CMD_CIPHER_CTRL_S *cmd_cipher_ctrl = (CMD_CIPHER_CTRL_S *)sbuf;
	CMD_CIPHER_PROCESS_S *cmd_cipher_process = (CMD_CIPHER_PROCESS_S *)sbuf;
	CMD_HASH_INIT_S *cmd_hash_init = (CMD_HASH_INIT_S *)sbuf;
	CMD_HASH_UPDATE_S *cmd_hash_update = (CMD_HASH_UPDATE_S *)sbuf;
	CMD_HASH_FINAL_S *cmd_hash_final = (CMD_HASH_FINAL_S *)sbuf;
	CMD_HASH_DIGEST_S *cmd_hash_dgst = (CMD_HASH_DIGEST_S *)sbuf;
	CMD_GET_HASHINFO_S *cmd_get_hashinfo = (CMD_GET_HASHINFO_S *)sbuf;
    int   ret;

	FH_U8 dgst[32];
	FH_U32 dgstlen;

	switch (_IOC_DIR(cmd))
	{
    case _IOC_NONE:
        parg = NULL;
        break;
    case _IOC_READ:
    case _IOC_WRITE:
    case (_IOC_WRITE | _IOC_READ):
		if (_IOC_SIZE(cmd) > sizeof(sbuf))
			return -ENOMEM;

        if (_IOC_DIR(cmd) & _IOC_WRITE)
            if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
                return -EFAULT;

        break;
	}



	switch(cmd)
	{
	case CMD_CIPHER_ENCRYPT:
	case CMD_CIPHER_DECRYPT:
		if (cmd_cipher_process->DataPkgNum != 1)
			return FH_ERR_CIPHER_INVALID_PARA;

		cmd_cipher_process->pKernelDataPkg = kmalloc(sizeof(FH_UNF_CIPHER_DATA_S), GFP_KERNEL);
		if (cmd_cipher_process->pKernelDataPkg == NULL)
			return -ENOMEM;

		if (copy_from_user(cmd_cipher_process->pKernelDataPkg,
			(void __user *)cmd_cipher_process->pDataPkg,
			sizeof(FH_UNF_CIPHER_DATA_S)))
			return -EFAULT;

		break;
	case CMD_CIPHER_ENCRYPTMULTI:
	case CMD_CIPHER_DECRYPTMULTI:
	case CMD_CIPHER_ENCRYPTMULTI_EX:
	case CMD_CIPHER_DECRYPTMULTI_EX:
		if (cmd_cipher_process->DataPkgNum == 0
			|| cmd_cipher_process->DataPkgNum > 128)
			return FH_ERR_CIPHER_INVALID_PARA;

		cmd_cipher_process->pKernelDataPkg = kmalloc(cmd_cipher_process->DataPkgNum * sizeof(FH_UNF_CIPHER_DATA_S), GFP_KERNEL);
		if (cmd_cipher_process->pKernelDataPkg == NULL)
			return -ENOMEM;
			
		if (copy_from_user(cmd_cipher_process->pKernelDataPkg,
			(void __user *)cmd_cipher_process->pDataPkg,
			cmd_cipher_process->DataPkgNum * sizeof(FH_UNF_CIPHER_DATA_S)))
			return -EFAULT;
	case CMD_CIPHER_CALCHASHINIT:
		if (cmd_hash_init->hmackey) {
			cmd_hash_init->khmackey = kmalloc(HASH_BLOCK_SIZE, GFP_KERNEL);
			if (cmd_hash_init->khmackey == NULL)
				return -ENOMEM;

			if (copy_from_user(cmd_hash_init->khmackey,
				(void __user *)cmd_hash_init->hmackey, HASH_BLOCK_SIZE))
				return -EFAULT;
		}
		break;
	case CMD_CIPHER_CALCHASHUPDATE:
		cmd_hash_update->kmsg = kmalloc(cmd_hash_update->msglen, GFP_KERNEL);
		if (cmd_hash_update->kmsg == NULL)
			return -ENOMEM;

		if (copy_from_user(cmd_hash_update->kmsg,
			(void __user *)cmd_hash_update->msg,
			cmd_hash_update->msglen))
			return -EFAULT;
		break;
	}



	switch(cmd)
	{
#ifdef CIPHER_MULTICIPHER_SUPPORT
		case CMD_CIPHER_CREATEHANDLE:
			ret = FH_DRV_CIPHER_CreateHandle(&cmd_cipher_create->hdlr, &g_cesa);
			break;
		case CMD_CIPHER_DESTROYHANDLE:
			ret = FH_DRV_CIPHER_DestroyHandle(cmd_cipher_destroy->hdlr);
			break;
		case CMD_CIPHER_CONFIGHANDLE:
			ret = FH_DRV_CIPHER_Config(cmd_cipher_ctrl->hdlr , &cmd_cipher_ctrl->ctrl);
			break;
		case CMD_CIPHER_ENCRYPT:
			ret = FH_DRV_CIPHER_Encrypt(cmd_cipher_process->hdlr,
										cmd_cipher_process->pKernelDataPkg[0].u32SrcPhyAddr,
										cmd_cipher_process->pKernelDataPkg[0].u32DestPhyAddr,
										cmd_cipher_process->pKernelDataPkg[0].u32ByteLength);
			break;
		case CMD_CIPHER_DECRYPT:
			ret = FH_DRV_CIPHER_Decrypt(cmd_cipher_process->hdlr,
										cmd_cipher_process->pKernelDataPkg[0].u32SrcPhyAddr,
										cmd_cipher_process->pKernelDataPkg[0].u32DestPhyAddr,
										cmd_cipher_process->pKernelDataPkg[0].u32ByteLength);
			break;
		case CMD_CIPHER_ENCRYPTMULTI:
			ret = FH_DRV_CIPHER_EncryptMulti(cmd_cipher_process->hdlr,
											cmd_cipher_process->pKernelDataPkg,
											cmd_cipher_process->DataPkgNum);
			break;
		case CMD_CIPHER_DECRYPTMULTI:
			ret = FH_DRV_CIPHER_DecryptMulti(cmd_cipher_process->hdlr,
											cmd_cipher_process->pKernelDataPkg,
											cmd_cipher_process->DataPkgNum);
			break;
		case CMD_CIPHER_ENCRYPTMULTI_EX:
			ret = FH_DRV_CIPHER_Config(cmd_cipher_process->hdlr , &cmd_cipher_process->ctrl);
			if (FH_SUCCESS == ret)
			ret = FH_DRV_CIPHER_EncryptMulti(cmd_cipher_process->hdlr,
											cmd_cipher_process->pKernelDataPkg,
											cmd_cipher_process->DataPkgNum);
			break;
		case CMD_CIPHER_DECRYPTMULTI_EX:
			ret = FH_DRV_CIPHER_Config(cmd_cipher_process->hdlr , &cmd_cipher_process->ctrl);
			if (FH_SUCCESS == ret)
			ret = FH_DRV_CIPHER_DecryptMulti(cmd_cipher_process->hdlr,
											cmd_cipher_process->pKernelDataPkg,
											cmd_cipher_process->DataPkgNum);
			break;
#ifdef CIPHER_CCM_GCM_SUPPORT
		case CMD_CIPHER_GETTAG:
		{
			CIPHER_TAG *pstCITag = (CIPHER_TAG *)parg;

			ret = FH_DRV_CIPHER_GetTag(pstCITag);
			break;
		}  
#endif
		case CMD_CIPHER_GETHANDLECONFIG:
			ret = FH_DRV_CIPHER_GetHandleConfig(
				cmd_cipher_ctrl->hdlr,
				&cmd_cipher_ctrl->ctrl);
			break;
#ifdef CIPHER_KLAD_SUPPORT
		case CMD_CIPHER_KLAD_KEY:
		{
			CIPHER_KLAD_KEY_S *pstKladData =
				(CIPHER_KLAD_KEY_S *)parg;

			ret = FH_DRV_CIPHER_KladEncryptKey(pstKladData);
			break;
		}  
#endif
#endif
#ifdef CIPHER_RNG_SUPPORT
		case CMD_CIPHER_GETRANDOMNUMBER:
		{
			CIPHER_RNG_S *pstRNG  = (CIPHER_RNG_S *)parg;

			ret = FH_DRV_CIPHER_GetRandomNumber(pstRNG);
			break;
		}
#endif
#ifdef CIPHER_HASH_SUPPORT
		case CMD_CIPHER_CALCHASHINIT:
			ret = FH_DRV_CIPHER_CalcHashInit(
				    &cmd_hash_init->hdlr,
				    &g_cesa,
				    cmd_hash_init->alg,
				    cmd_hash_init->khmackey);
			break;
		case CMD_CIPHER_CALCHASHUPDATE:
			ret = FH_DRV_CIPHER_CalcHashUpdate(
				    cmd_hash_update->hdlr,
				    cmd_hash_update->kmsg,
				    cmd_hash_update->msglen);
			break;
		case CMD_CIPHER_CALCHASHFINAL:
			ret = FH_DRV_CIPHER_CalcHashFinal(
				    cmd_hash_final->hdlr,
				    dgst,
				    &dgstlen);
			if (ret == FH_SUCCESS) {
				if (copy_to_user((void __user *)cmd_hash_final->output, dgst, dgstlen))
					ret = -EFAULT;
			}
			break;
		case CMD_CIPHER_CALCHASH:
			ret = FH_DRV_CIPHER_CalcHash(
				    &g_cesa,
				    cmd_hash_dgst->alg,
				    cmd_hash_dgst->u32MsgPhyAddr,
				    cmd_hash_dgst->msglen,
				    dgst,
				    &dgstlen);
			if (ret == FH_SUCCESS) {
				if (copy_to_user((void __user *)cmd_hash_dgst->output, dgst, dgstlen))
					ret = -EFAULT;
			}
			break;
		case CMD_CIPHER_GET_HASHINFO:
			ret = FH_DRV_CIPHER_GetHashInfo(
				    cmd_get_hashinfo->hdlr,
				    &cmd_get_hashinfo->kalg,
				    &cmd_get_hashinfo->khmackey);
			if (ret == FH_SUCCESS) {
				if (copy_to_user((void __user *)cmd_get_hashinfo->alg, &cmd_get_hashinfo->kalg, 4))
					ret = -EFAULT;
				if (copy_to_user((void __user *)cmd_get_hashinfo->hmackey, cmd_get_hashinfo->khmackey, HASH_BLOCK_SIZE))
					ret = -EFAULT;
			}
			break;
#endif
#ifdef CIPHER_RSA_SUPPORT
		case CMD_CIPHER_CALCRSA:
		{
			CIPHER_RSA_DATA_S *pCipherRsaData =
				(CIPHER_RSA_DATA_S *)parg;
			
			ret = FH_DRV_CIPHER_CalcRsa(pCipherRsaData);
			break;
		}
#endif

		default:
			ret = -EINVAL;
			break;
	}



	switch(cmd) {
	case CMD_CIPHER_ENCRYPT:
	case CMD_CIPHER_DECRYPT:
	case CMD_CIPHER_ENCRYPTMULTI:
	case CMD_CIPHER_DECRYPTMULTI:
	case CMD_CIPHER_ENCRYPTMULTI_EX:
	case CMD_CIPHER_DECRYPTMULTI_EX:
		kfree(cmd_cipher_process->pKernelDataPkg);
		break;
	case CMD_CIPHER_CALCHASHINIT:
		if (cmd_hash_init->khmackey)
			kfree(cmd_hash_init->khmackey);
		break;
	case CMD_CIPHER_CALCHASHUPDATE:
		kfree(cmd_hash_update->kmsg);
		break;
	}


	
	if (ret) {
		if (ret > 0)
			return ret;
		else
			return -ret;
	}



    /*  copy results into user buffer  */
	switch (_IOC_DIR(cmd)) {
		case _IOC_READ:
		case (_IOC_WRITE | _IOC_READ):
			if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
				return -EFAULT;
			break;
	}

	return 0;
}


static const struct file_operations cipher_fops = {
	.owner		= THIS_MODULE,
	.open		= cipher_open,
	.unlocked_ioctl	= cipher_ioctl,
	.release	= cipher_release
};

static struct miscdevice cipher_miscdev = {
	.fops		= &cipher_fops,
	.name		= "cipher",
	.minor		= CIPHER_MINOR,
};

static const struct of_device_id cipher_of_match_table[] = {
	{ .compatible = "fh, fh-cesa", .data = 0/*&rpu_caps*/ },
	{}
};
MODULE_DEVICE_TABLE(of, cipher_of_match_table);

static irqreturn_t cipher_int(int irq, void *priv)
{
	irqreturn_t ret = IRQ_NONE;
	CESA_RES_S *cesa = (CESA_RES_S *)priv;

	while (true) {
		if (rpu_get_int_status(cesa->regs + RPU_REG_OFFSET) == 0) {
			printk("rpu work not finished!!!\n");
			break;
		}
		rpu_clr_irq(cesa->regs + RPU_REG_OFFSET);
		rpu_stop_irq(cesa->regs + RPU_REG_OFFSET);
		complete(&cesa->completion);
		ret = IRQ_HANDLED;
		break;
	}

	return ret;
}

static int cipher_drv_probe(struct platform_device *pdev)
{
	const struct of_device_id *match;
	struct device *dev = &pdev->dev;
	struct resource *res;
	int ret;

	if (dev->of_node) {
		//match = of_match_device(fh_cesa_of_match_table, dev);
		match = of_match_node(cipher_of_match_table, dev->of_node);
		if (!match /*|| !match->data*/)
			return -ENOTSUPP;
		//caps = match->data;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	g_cesa.regs = devm_ioremap_resource(dev, res);
	if (IS_ERR(g_cesa.regs))
		return PTR_ERR(g_cesa.regs);

	g_cesa.irq = platform_get_irq(pdev, 0);
	if (g_cesa.irq < 0)
		return g_cesa.irq;

	g_cesa.clk = clk_get(NULL, "cesa_clk");
	if (IS_ERR(g_cesa.clk))
		return PTR_ERR(g_cesa.clk);

	ret = devm_request_threaded_irq(dev,
					g_cesa.irq,
					NULL,
					cipher_int,
					IRQF_ONESHOT,
					dev_name(&pdev->dev),
					&g_cesa);
	if (ret)
		goto out_put_clk;

	clk_set_rate(g_cesa.clk, 5000000);
	ret = clk_prepare_enable(g_cesa.clk);
	if (ret)
		goto out_release_irq;

	ret = misc_register(&cipher_miscdev);
	if (ret)
		goto out_disable_clk;

	init_completion(&g_cesa.completion);
	mutex_init(&g_cesa.lock);

	g_cesa.st = RPU_ST_BUTT,
	g_cesa.curr_hdlr = NULL,
	g_cesa.dev = &pdev->dev; /* cipher_miscdev.this_device */

	return 0;

out_disable_clk:
	clk_disable(g_cesa.clk);
out_release_irq:
	free_irq(g_cesa.irq, &g_cesa);
	irq_dispose_mapping(g_cesa.irq);
out_put_clk:
	clk_put(g_cesa.clk);

	return ret;
}

static int cipher_drv_remove(struct platform_device *pdev)
{
	misc_deregister(&cipher_miscdev);
	clk_disable(g_cesa.clk);
	clk_put(g_cesa.clk);
	free_irq(g_cesa.irq, &g_cesa);
	irq_dispose_mapping(g_cesa.irq);
	memset(&g_cesa, 0, sizeof(CESA_RES_S));

	return 0;
}

static struct platform_driver cipher_driver = {
	.probe		= cipher_drv_probe,
	.remove		= cipher_drv_remove,
	.driver		= {
		.name	= "fh-cesa",
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(cipher_of_match_table),
	},
};
module_platform_driver(cipher_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Circle Xue <xuejy344@fullhan>");
MODULE_DESCRIPTION("Fullhan's cryptographic engine driver");

